/***************************************************************************
 *   Copyright (C) 2008 by Chris Bornholdt   *
 *   chris.bornholdt@gmail.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#ifndef _THREAD_MANAGER_HPP_
#define _THREAD_MANAGER_HPP_

#include <QObject>
#include <QThread>
#include <QMutexLocker>
#include <QVariant>

#include <tr1/functional>
#include <tr1/memory>

namespace tr1 = std::tr1;
using tr1::shared_ptr;

template<typename Functor, typename Input>
struct SharedFunctorWrapper
{
	typedef typename Functor::result_type result_type;
	
	SharedFunctorWrapper(shared_ptr<Functor> f, Input i) : f_(f), i_(i) {}
	
	result_type operator()()
	{	return (*f_)(i_);	}
	
	private:
		shared_ptr<Functor> f_;
		Input i_;
};

template<typename Functor, typename Input>
SharedFunctorWrapper<Functor, Input> makeSharedFunctorWrapper(shared_ptr<Functor> f, 
	Input i)
{	return SharedFunctorWrapper<Functor, Input>(f, i);	}

namespace detail
{

//wrap actions so that the output is changed to an input reference ...
template<typename R>
struct ThreadActionWrapper
{
	typedef void result_type;
	typedef QVariant& argument_type;

	ThreadActionWrapper(tr1::function<R ()> f) : f_(f) {}
	
	void operator()(QVariant &var)
	{	var = QVariant::fromValue(f_());	}
	
	private:
		tr1::function<R ()> f_;
};

template<>
struct ThreadActionWrapper<void>
{
	typedef void result_type;
	typedef QVariant& argument_type;
	
	ThreadActionWrapper(tr1::function<void ()> f) : f_(f) {}
	
	void operator()(QVariant&)
	{	f_();	}
			
	private:
		tr1::function<void ()> f_;
};
		
};	//end namespace detail

class QMutex;

class ThreadObject : public QThread
{
	Q_OBJECT
	public:
		ThreadObject();
		~ThreadObject();
		
		void apply(tr1::function<void (QVariant &)> action);
		
		QVariant variant() const;
		void clearVariant();		//	ensures any output from Thread is not needlessly held
		
		bool isComplete() const;	//	differs from isFinished() in that ThreadManager flags
		void setComplete();		//	the operation complete allowing the variant to be copied
		
	protected:
		void run();
		
	private:
		ThreadObject(const ThreadObject &);	//instead of boost::noncopyable
		const ThreadObject& operator=(const ThreadObject &);
	
		tr1::function<void (QVariant &)> action_;
		QVariant var_;
		bool complete_;
};

/*	when using ThreadManager, make sure to explicitly declare the first template argument to scheduleAction,
	so that the return type is properly determined.  Also, this type must be a Q_DECLARED_METATYPE or base QT 
	object at the point of instantiation of scheduleAction				*/

class ThreadManager : public QObject
{
	Q_OBJECT
	public:
		enum Priority { LowPriority, NormalPriority, HighPriority, ImmediatePriority };
		
		enum WarningLevel { NoWarning, Warning, Debug };
		
		friend bool operator<(Priority left, Priority right)
		{	return int(left) < int(right);	}
	
		ThreadManager(QObject *parent = 0, WarningLevel level = NoWarning, int desiredThreads = 0);
		
		~ThreadManager();
		
		static ThreadManager& instance();	//for ease of access for classes that need thread manager
		
		typedef unsigned int ActionId;
		typedef tr1::function<void (QVariant&)> WrappedAction;
			
		template<typename R, typename T>
		ActionId scheduleAction(tr1::function<R ()> action, QObject *reciever, 
			const char* method, T data, Priority priority = NormalPriority)
		{
			int returnValue;
			{
				QMutexLocker lock(mutex_);
				tr1::function<void (QVariant&)> wrappedAction = 
					detail::ThreadActionWrapper<R>(action);
					//(lambda::_1 = lambda::bind(&QVariant::fromValue<R>, lambda::bind(action)));
				actionDataList_.append(ActionData(++actionIdCounter_, priority, wrappedAction, 
					reciever, method, QVariant::fromValue(data)));
				returnValue = actionIdCounter_;
			}
			
			if(priority == ImmediatePriority)	//bypass everything and start now
				startNewAction();
			
			updateRunningActions();
			return returnValue;
		}
		
		template<typename R>
		ActionId scheduleAction(tr1::function<R ()> action, QObject *reciever, 
			const char* method, Priority priority = NormalPriority)
		{
			int returnValue;
			{
				QMutexLocker lock(mutex_);
				tr1::function<void (QVariant&)> wrappedAction = 
					detail::ThreadActionWrapper<R>(action);
					//(lambda::_1 = lambda::bind(&QVariant::fromValue<R>, lambda::bind(action)));
				actionDataList_.append(ActionData(++actionIdCounter_, priority, wrappedAction, 
					reciever, method, QVariant()));
				returnValue = actionIdCounter_;
			}
			
			if(priority == ImmediatePriority)	//bypass everything and start now
				startNewAction();
				
			updateRunningActions();
			return returnValue;
		}
		
		template<typename T>
		ActionId scheduleAction(tr1::function<void ()> action, QObject *reciever, 
			const char* method, T data, Priority priority = NormalPriority)
		{
			int returnValue;
			{
				QMutexLocker lock(mutex_);
				tr1::function<void (QVariant&)> wrappedAction = 
					detail::ThreadActionWrapper<void>(action);
				actionDataList_.append(ActionData(++actionIdCounter_, priority, wrappedAction, 
					reciever, method, QVariant::fromValue(data)));
				returnValue = actionIdCounter_;
			}
			
			if(priority == ImmediatePriority)	//bypass everything and start now
				startNewAction();
				
			updateRunningActions();
			return returnValue;
		}
		
		ActionId scheduleAction(tr1::function<void ()> action, QObject *reciever, 
			const char* method, Priority priority = NormalPriority)
		{
			int returnValue;
			{
				QMutexLocker lock(mutex_);
				tr1::function<void (QVariant&)> wrappedAction = 
					detail::ThreadActionWrapper<void>(action);
				actionDataList_.append(ActionData(++actionIdCounter_, priority, wrappedAction, 
					reciever, method, QVariant()));
				returnValue = actionIdCounter_;	
			}
			
			if(priority == ImmediatePriority)	//bypass everything and start now
				startNewAction();
				
			updateRunningActions();
			return returnValue;
		}
		
	private slots:
		void updateRunningActions();
				
		void updateThreadData();	//only called by QThread finished/terminated
		
		void startNewAction();
			
	private:
		struct ActionData
		{
			ActionId id;
			Priority priority;
			WrappedAction action;
			QObject *reciever;
			const char* method;
			QVariant data;
			ThreadObject *thread;
			
			ActionData(ActionId i, Priority p, WrappedAction w, QObject *r, const char *m, 
				QVariant d) : id(i), priority(p), action(w), reciever(r), method(m), 
				data(d), thread(0) {}
		};
		
		void actionFinished(ActionData& action);
		
		static ActionId actionIdCounter_;
		
		QMutex *mutex_;
		QList<ActionData> actionDataList_;
		
		QList<ThreadObject *> threadObjects_;
		int runningThreads_;
		int desiredThreadCount_;
		WarningLevel warningLevel_;
};

#endif
